﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace Testcraft
{
    class FirstPersonCamera : ICamera
    {
        private Game game;
        public Point mouseResetPosition;
        private float near, far;
        private float aspectRatio;
        private float fov;
        private BoundingFrustum frustum;
        private Matrix view, proj;
        private bool viewIsDirty, projIsDirty, frustumIsDirty;
        private Vector3 position, target, up;
        private Vector2 rotation;

        public BoundingFrustum Frustum
        {
            get
            {
                if (frustumIsDirty)
                    this.BuildFrustum();

                return frustum;
            }
            private set
            {
                this.frustum = value;
            }
        }

        public float NearPlane
        {
            get { return this.near; }
            set
            {
                this.near = value;
                this.frustumIsDirty = true;
                this.projIsDirty = true;
            }
        }

        public float FarPlane
        {
            get { return this.far; }
            set
            {
                this.far = value;
                this.frustumIsDirty = true;
                this.projIsDirty = true;
            }
        }

        public float AspectRatio
        {
            get { return this.aspectRatio; }
            set
            {
                this.aspectRatio = value;
                this.frustumIsDirty = true;
                this.projIsDirty = true;
            }
        }

        public float FieldOfView
        {
            get { return this.fov;}
            set
            {
                this.fov = value;
                this.frustumIsDirty = true;
                this.projIsDirty = true;
            }
        }

        public Matrix Projection
        {
            get
            {
                if(this.projIsDirty)
                    this.BuildProjection();

                return this.proj;
            }
        }

        public Matrix View
        {
            get
            {
                if(this.viewIsDirty)
                    this.BuildView();

                return this.view;
            }
        }

        public Vector3 Position
        {
            get { return this.position; }
            set
            {
                this.position = value;
                this.frustumIsDirty = true;
                this.viewIsDirty = true;
            }
        }

        public Vector3 Target
        {
            get { return this.target; }
            set
            {
                this.target = value;
                this.frustumIsDirty = true;
                this.viewIsDirty = true;
            }
        }

        public Vector3 Up
        {
            get { return this.up; }
            set
            {
                this.up = value;
                this.frustumIsDirty = true;
                this.viewIsDirty = true;
            }
        }

        public FirstPersonCamera(Game game)
        {
            this.game = game;
            this.frustumIsDirty = true;
            this.viewIsDirty = true;
            this.projIsDirty = true;
        }

        private void BuildFrustum()
        {
            this.frustumIsDirty = false;
            this.Frustum = new BoundingFrustum(this.View * this.Projection);
        }

        private void BuildProjection()
        {
            this.projIsDirty = false;
            Matrix.CreatePerspectiveFieldOfView(this.fov, this.aspectRatio, this.near, this.far, out this.proj);
        }

        private void BuildView()
        {
            this.viewIsDirty = false;
            Matrix.CreateLookAt(ref this.position, ref this.target, ref this.up, out this.view);
        }

        public void Update(GameTime gameTime)
        {
            const float speed = 0.25f;
            MouseState mouseState = Mouse.GetState();
            float deltaX = (float)((mouseState.X - mouseResetPosition.X) * gameTime.ElapsedGameTime.TotalSeconds * speed);
            float deltaY = (float)((mouseState.Y - mouseResetPosition.Y) * gameTime.ElapsedGameTime.TotalSeconds * speed);
            this.rotation.Y = (this.rotation.Y - deltaX) % MathHelper.TwoPi;
            this.rotation.X = MathHelper.Clamp((this.rotation.X - deltaY), -MathHelper.PiOver2, MathHelper.PiOver2);
            
            Matrix world = Matrix.CreateRotationY(this.rotation.Y);
            world *= Matrix.CreateFromAxisAngle(world.Right, this.rotation.X);
            Vector3 forward = Vector3.Forward;
            Vector3 up = Vector3.Up;
            Vector3.Transform(ref forward, ref world, out forward);
            Vector3.Transform(ref up, ref world, out up);
            this.Target = this.position + forward;
            this.Up = up;

            if(this.game.IsActive)
                Mouse.SetPosition(mouseResetPosition.X, mouseResetPosition.Y);
        }
    }
}
